/* Copyright (c) 2019 285424336@qq.com.
 * All rights reserved.
 * Author: 285424336
 * License: LGPLV3
 */
#include <glib.h>

#include <cstdlib>
#include <memory>

#ifdef G_OS_WIN32
#ifdef USE_VLD
#include <vld.h>
#endif
#endif

static std::shared_ptr<GMainLoop> main_loop;

static void elapsed_timer_example();
static gboolean g_timeout_add_source_func(gpointer user_data);
static gboolean g_timeout_add_full_source_func(gpointer user_data);
static void g_timeout_add_full_destroy_notify(gpointer data);
static gboolean g_source_set_callback_source_func(gpointer user_data);
static void g_source_set_callback_destroy_notify(gpointer data);
static void timeout_timer_example();

/**
 * @brief main 程序入口函数
 * @param argc 启动参数个数
 * @param argv 启动参数数组
 * @return 程序返回值
 */
int main(int argc, char** argv) {
    (void)argc;
    (void)argv;
    // 创建GMainLoop数据结构
    main_loop.reset(g_main_loop_new(nullptr, FALSE), [](GMainLoop* loop) {
        if (nullptr != loop) {
            g_main_loop_unref(loop);
        }
    });
    // 检查表达式是否为非nullptr的调试宏
    // 如果断言失败(也就是表达式为nullptr)，错误信息被记录并且应用程序不是被终止就是测试用例标志失败
    g_assert_nonnull(main_loop.get());
    elapsed_timer_example();
    timeout_timer_example();
    // 运行主循环直到g_main_loop_quit()在循环中被调用
    // 如果这个函数在循环的GMainContext所属运行线程中被调用，它将处理循环中的事件，否则它仅是简单等待
    g_main_loop_run(main_loop.get());
    return EXIT_SUCCESS;
}

/**
 * @brief elapsed_timer_example 测试计时器例子
 */
static void elapsed_timer_example() {
    // g_timer_new创建一个新的计时器，并开始计时(也就是g_timer_start()隐示为你调用)
    std::shared_ptr<GTimer> elapsed_timer(g_timer_new(), [](GTimer* elapsed_timer){
        if (nullptr != elapsed_timer) {
            // g_timer_destroy销毁计时器，释放相关资源
            g_timer_destroy(elapsed_timer);
        }
    });
    // 暂停当前调用线程给定微秒数。
    // 1秒有100万微秒，以G_USEC_PER_SEC宏为代表，g_usleep()可能精度有限，依赖硬件和操作系统，不要依赖睡眠的准确长度
    g_usleep(1000 * 1000);
    // 标记结束时间，以便调用g_timer_elapsed将返回结束时间和开始时间的差值
    g_timer_stop(elapsed_timer.get());
    gdouble elpased = 0;
    // 如果计时器已经开始但是没有停止，获得时间为从计时器开始计时到现在的时间。
    // 如果计时器已被停止，获得计时时间为计时器停止至计时器开始的差值
    // 返回值为计时秒数，包含小数部分。输出参数microseconds基本不适用。
    elpased = g_timer_elapsed(elapsed_timer.get(), nullptr);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "elpased %d = %f\n", __LINE__, elpased);
    // 恢复上次被g_timer_stop()停止的计时器。在使用这个函数之前g_timer_stop()必须被调用
    g_timer_continue(elapsed_timer.get());
    g_usleep(1000 * 1000);
    elpased = g_timer_elapsed(elapsed_timer.get(), nullptr);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "elpased %d = %f\n", __LINE__, elpased);
    // 这个函数是没有用途的，比较好的做法是在已经开始计时的计时器上调用g_timer_start来重置开始计时时间，所以g_timer_reset()就当没有任何用途。
    g_timer_reset(elapsed_timer.get());
    // 标记一个开始时间，以便将来调用g_timer_elapsed()可以报告从g_timer_start被调用到现在经历的时间。
    // g_timer_new()会自动标记开始时间，所以不需要在创建计时器后立马调用g_timer_start()。
    g_timer_start(elapsed_timer.get());
    g_usleep(1000 * 1000);
    elpased = g_timer_elapsed(elapsed_timer.get(), nullptr);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "elpased %d = %f\n", __LINE__, elpased);
}

/**
 * @brief g_timeout_add_source_func 超时计时器超时回调函数
 * @param user_data 回调数据
 * @return 返回FALSE移除事件源，G_SOURCE_CONTINUE和G_SOURCE_REMOVE是更难忘记名字的返回值
 */
static gboolean g_timeout_add_source_func(gpointer user_data) {
    static unsigned int count = 0;
    gboolean ret = G_SOURCE_CONTINUE;
    ++count;
    GMainContext* main_context = static_cast<GMainContext*>(user_data);
    if (g_main_context_default() != main_context) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_main_context_default() != main_context\n");
    }
    // 返回当前线程出发的事件源或者为空
    GSource* source = g_main_current_source();
    if (nullptr != source) {
        // 返回特定事件源的数字id，事件源在特定GMainLoop的GMainContext上这个id是个唯一正整数。
        // 反过来从特定的id映射到事件源是通过g_main_context_find_source_by_id()完成的。
        guint timeout_id = g_source_get_id(source);
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "callback timeout_id = %d\n", timeout_id);
    }
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "count = %d\n", count);
    if (count > 2) {
        ret = G_SOURCE_REMOVE;
        g_main_loop_quit(main_loop.get());
    }
    return ret;
}

/**
 * @brief g_timeout_add_full_source_func 超时计时器超时回调函数
 * @param user_data 回调数据
 * @return 返回FALSE移除事件源，G_SOURCE_CONTINUE和G_SOURCE_REMOVE是更难忘记名字的返回值
 */
static gboolean g_timeout_add_full_source_func(gpointer user_data) {
    static unsigned int count = 0;
    // 使用这个宏作为GSourceFunc返回值来从对应的GMainLoop中留下指定事件源
    gboolean ret = G_SOURCE_CONTINUE;
    ++count;
    GMainContext* main_context = static_cast<GMainContext*>(user_data);
    if (g_main_context_default() != main_context) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_main_context_default() != main_context\n");
    }
    // 返回当前线程出发的事件源或者空
    GSource* source = g_main_current_source();
    if (nullptr != source) {
        // 返回特定事件源的数字id，事件源在特定GMainLoop的GMainContext上这个id是个唯一正整数。
        // 反过来从特定的id映射到事件源是通过g_main_context_find_source_by_id()完成的。
        guint timeout_id = g_source_get_id(source);
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "callback timeout_id = %d\n", timeout_id);
    }
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "count = %d\n", count);
    if (count > 2) {
        // 使用这个宏作为GSourceFunc返回值来从对应的GMainLoop中移除指定事件源
        ret = G_SOURCE_REMOVE;
    }
    return ret;
}

/**
 * @brief g_timeout_add_full_destroy_notify 超时计时器销毁通知函数
 * @param data 回调数据
 */
static void g_timeout_add_full_destroy_notify(gpointer data) {
    GMainContext* main_context = static_cast<GMainContext*>(data);
    if (g_main_context_default() != main_context) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_main_context_default() != main_context\n");
    }
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_timeout_add_full_destroy_notify\n");
}


/**
 * @brief g_source_set_callback_source_func事件源就绪回调函数
 * @param user_data 回调数据
 * @return 返回FALSE移除事件源，G_SOURCE_CONTINUE和G_SOURCE_REMOVE是更难忘记名字的返回值
 */
static gboolean g_source_set_callback_source_func(gpointer user_data) {
    static unsigned int count = 0;
    // 使用这个宏作为GSourceFunc返回值来从对应的GMainLoop中留下指定事件源
    gboolean ret = G_SOURCE_CONTINUE;
    ++count;
    GMainContext* main_context = static_cast<GMainContext*>(user_data);
    if (g_main_context_default() != main_context) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_main_context_default() != main_context\n");
    }
    // 返回当前线程出发的事件源或者空
    GSource* source = g_main_current_source();
    if (nullptr != source) {
        // 返回特定事件源的数字id，事件源在特定GMainLoop的GMainContext上这个id是个唯一正整数。
        // 反过来从特定的id映射到事件源是通过g_main_context_find_source_by_id()完成的。
        guint timeout_id = g_source_get_id(source);
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "callback timeout_id = %d\n", timeout_id);
    }
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "count = %d\n", count);
    if (count > 2) {
        // 使用这个宏作为GSourceFunc返回值来从对应的GMainLoop中移除指定事件源
        ret = G_SOURCE_REMOVE;
    }
    return ret;
}

/**
 * @brief g_source_set_callback_destroy_notify 事件源销毁回调通知
 * @param data 回调数据
 */
static void g_source_set_callback_destroy_notify(gpointer data) {
    GMainContext* main_context = static_cast<GMainContext*>(data);
    if (g_main_context_default() != main_context) {
        g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_main_context_default() != main_context\n");
    }
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "g_timeout_add_full_destroy_notify\n");
}


/**
 * @brief timeout_timer 超时定时器例子
 */
static void timeout_timer_example() {
    guint interval = 1000;
    // 返回全局默认GMainContext。
    // 当GMainLoop没用显示指定GMainContext时将使用这个GMainContext，对应于主GMainLoop。
    // 请参考g_main_context_get_thread_default()。
    gpointer data = static_cast<gpointer>(g_main_context_default());

    // 设置一个函数每隔一定时间被调用，默认优先级是G_PRIORITY_DEFAULT。
    // 这个函数被重复调用直至它返回FALSE，在这一点上超时计时器将被自动销毁并且函数将不再被调用。
    // 第一个间隔结束之后这函数将被第一次调用。
    // 由于处理其它事件源，请注意超时函数也许会被延迟调用。
    // 因此他们不应该被用来精确计时。
    // 每一次调用超时函数之后，下一次超时时间将基于当前时间和给定间隔重新计算（它不会试图赶上在延迟处理中丢失的时间）。
    // 请参考事件源内存管理中关于怎么样处理返回值和数据内存管理的细节。
    // 如果你想有个秒级别的超时定时器并且不关心它第一次调用的准确时间，可以使用g_timeout_add_seconds()；
    // 该函数提供了更优化和更高效节能使用系统电源。
    // 该函数内部使用g_timeout_source_new()创建了一个GMainLoop事件源并且使用g_source_attach()附加到全局GMainContext，
    // 所以回调函数将在运行主GMainContext的线程中调用。
    // 如果你需要大的控制权或者使用自定义GMainContext，你可以手动操作这些步骤。
    // 给定的间隔是以绝对时间来的，并不是相对时间。
    // 请参阅g_get_monotonic_time()。
    guint timeout_id = 0;
    //    timeout_id = g_timeout_add_seconds(1, g_timeout_add_source_func, data);
    //    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "timeout_id = %d\n", timeout_id);
    timeout_id = g_timeout_add(interval, g_timeout_add_source_func, data);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "timeout_id = %d\n", timeout_id);

    // 设置一个函数每隔一定时间被调用，以给定的优先级。
    // 这个函数被重复调用直至它返回FALSE，在这一点上超时计时器将被自动销毁并且函数将不再被调用。
    // 当超时计时器销毁时notify函数将被调用。
    // 第一个间隔结束之后这函数将被第一次调用。
    // 由于处理其它事件源，请注意超时函数也许会被延迟调用。
    // 因此他们不应该被用来精确计时。
    // 每一次调用超时函数之后，下一次超时时间将基于当前时间和给定间隔重新计算（它不会试图赶上在延迟处理中丢失的时间）。
    // 请参考事件源内存管理中关于怎么样处理返回值和数据内存管理的细节。
    // 如果你想有个秒级别的超时定时器并且不关心它第一次调用的准确时间，可以使用g_timeout_add_seconds()；
    // 该函数提供了更优化和更高效节能使用系统电源。
    // 该函数内部使用g_timeout_source_new()创建了一个GMainLoop事件源并且使用g_source_attach()附加到全局GMainContext，
    // 所以回调函数将在运行主GMainContext的线程中调用。
    // 如果你需要大的控制权或者使用自定义GMainContext，你可以手动操作这些步骤。
    // 给定的间隔是以绝对时间来的，并不是相对时间。
    // 请参阅g_get_monotonic_time()。
    //    timeout_id = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
    //                                            1,
    //                                            g_timeout_add_full_source_func,
    //                                            data,
    //                                            g_timeout_add_full_destroy_notify);
    //    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "timeout_id = %d\n", timeout_id);
    timeout_id = g_timeout_add_full(G_PRIORITY_DEFAULT,
                                    interval,
                                    g_timeout_add_full_source_func,
                                    data,
                                    g_timeout_add_full_destroy_notify);
    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "timeout_id = %d\n", timeout_id);
    // 创建一个超时计时器事件源。
    // 该事件源将不会初始化关联任何GMainContext，并且在它执行之前必须使用g_source_attach()添加到一个GMainContext中。
    // 给定的间隔是以绝对时间来的，并不是相对时间。
    // 请参阅g_get_monotonic_time()。
    GSource* source = nullptr;
    source = g_timeout_source_new(interval);
    g_assert_nonnull(source);
    g_source_set_callback(source, g_source_set_callback_source_func, data, g_source_set_callback_destroy_notify);
    // GSource引用计数减一。
    // 如果减一后引用计数结果是零，则GSource和关联内存将被销毁。
    g_source_unref(source);
    // 添加一个GSource至一个GMainContext以至于它在GMainContext中可以被执行。
    // 通过调用g_source_destroy()来移除它。
    //    timeout_id = g_source_attach(source, g_main_context_default());
    //    g_log(__FUNCTION__, G_LOG_LEVEL_MESSAGE, "timeout_id = %d\n", timeout_id);
    //    source = g_timeout_source_new_seconds(1);
    //    g_assert_nonnull(source);
    //    g_source_set_callback(source, g_source_set_callback_source_func, data, g_source_set_callback_destroy_notify);
    //    timeout_id = g_source_attach(source, g_main_context_default());
}
